package model;

import java.util.ArrayList;
import java.util.LinkedList;
import javax.swing.JOptionPane;
import java.util.Random;
import javax.swing.ImageIcon;
import miscellaneous.Dificultad;
import miscellaneous.Pair;



public class ModeloSudoku {
   
    private int aFila;
    private int aCol;
    private Random rnd = new Random();
    private int[] valoresAl;
    private int[] comp = new int[41];
    private static int count;
    public int[][] matriz = new int[9][9];
    public int[][] tablero2 = new int[9][9]; // tablero utilizado para cargar un sudoku resuelto
    int[][] tablero = new int[9][9];
    ArrayList<Pair<Integer,Integer>> posAleatorias = new ArrayList<Pair<Integer,Integer>>();
    private static ModeloSudoku instanciaGener = null;  
    public LinkedList partidasJugadas;
    private static Cronometro cronometro = Cronometro.getInstanciaCronometro();
    private static Boolean juegoTerminado = false;
    
    
    
    public static Boolean getJuegoTerminado() {
        return juegoTerminado;
    }
    
    
    public static ModeloSudoku getInstanciaGenSud() {
        if (instanciaGener == null){
            throw new NullPointerException();
        }
        return instanciaGener; 
   }
   
    public  static void crearInstanciaGenSud(){
        if (instanciaGener == null){
            instanciaGener = new ModeloSudoku();
        }
    }
   
    
    
    public int[][] cargar_juego(Dificultad dif) {
        int numVis = dif.getDificultad();
        Object[] arrSudRes = sudokusResueltos(1); // Arreglo de sudokus resueltos
        tablero2 = (int[][]) arrSudRes[0];
        Random rand = new Random();
        int numCargados = 0;
        while ( numCargados < numVis ) {
            int valorFila = rand.nextInt(9);
            int valorCol = rand.nextInt(9);
            Pair<Integer,Integer> filCol = new Pair<Integer,Integer>(valorFila,valorCol);
                
            if(!(posAleatorias.contains(filCol))){
                posAleatorias.add(filCol);                
                matriz[filCol.getFirst()][filCol.getSecond()] = tablero2[filCol.getFirst()][filCol.getSecond()];
                numCargados ++;
            }    
        }
      
        return matriz;
    }  
      
    
    // METODO PARA COMPROBAR LOS VALORES.

    public boolean comprobar_valor(String valor1) {
        if (String.valueOf(valor1).equalsIgnoreCase("")) {
            return false;
        } else {
            int valor = Integer.valueOf(valor1);
            if (valor >= 0 && valor < 10) {
                return true;
            } else {
                return false;
            }
        }

    }
    
    //METODO PARA COMPROBAR FILAS.

    public boolean existe_fila(int numero, int fila) {
        boolean resultado = false;
       // int a = matriz[0][2];
        for (int i = 0; i < matriz.length; i++) {
            if (numero == 0) {
            } else {
                if (matriz[fila][i] == numero) {
                    resultado = true;
                    break;
                }
            }
        }
        return resultado;

    }

    //METODO PARA COMPROBAR COLUMNAS.
    
    public boolean existe_columna(int numero, int columna) {

        boolean resultado = false;
        //int a = matriz[7][0];
        for (int i = 0; i < matriz.length; i++) {
            if (matriz[i][columna] == numero) {
                resultado = true;
                break;
            }
        }
        return resultado;
    }

 // METODO PARA DETERMINAR DIMENSION DE LA CAJA.
    
    public boolean existe_caja(int valor, int fila, int columna) {

        //VARIABLES.
        int minimo_fila;
        int maximo_fila;
        int minimo_columna;
        int maximo_columna;
        boolean resultado = false;

        //DETERMINAMOS LAS FILAS DE LA CAJA.
        if (fila >= 0 && fila < 3) {
            minimo_fila = 0;
            maximo_fila = 2;
        } else if (fila >= 3 && fila < 6) {
            minimo_fila = 3;
            maximo_fila = 5;
        } else {
            minimo_fila = 6;
            maximo_fila = 8;
        }

        //DETERMINAMOS LAS COLUMNAS DE LA CAJA.
        if (columna >= 0 && columna < 3) {
            minimo_columna = 0;
            maximo_columna = 2;
        } else if (columna >= 3 && columna < 6) {
            minimo_columna = 3;
            maximo_columna = 5;
        } else {
            minimo_columna = 6;
            maximo_columna = 8;
        }

        //RECORREMOS EL RANGO DE LA CAJA, Y BUSCAMOS EL VALOR.
        for (int f = minimo_fila; f <= maximo_fila; f++) {
            for (int c = minimo_columna; c <= maximo_columna; c++) {
                if (valor == 0) {
                } else {
                    if (matriz[f][c] == valor) {
                        resultado = true;
                        break;

                    }
                }
            }
        }
        //REGRESAMOS EL VALOR BOOLEANO.
        return resultado;



}


 
    //METODO QUE DETECTA SI EL JUEGO FUE TERMINADO.
    
    public static void terminado( int[][] matriz ){
       // boolean resultado = true;
        int cont = 0;
        for ( int f = 0; f < matriz.length; f ++){
            for ( int c = 0; c < matriz[0].length; c ++){
		if ( matriz[f][c] == 0 ){
                    cont = cont + 1;
                }
            }    
        }
        if (cont == 0){
            juegoTerminado = true;
            cronometro.pararCronometro();
            JOptionPane.showMessageDialog(null, "¡Ganaste!","Sudoku",JOptionPane.INFORMATION_MESSAGE,new ImageIcon("src/imagenes/ganaste.jpg"));
        }
        
    }

        
    
    // Método para limpiar el tablero del sudoku y dejarlo sin números dentro.
    public void limpiar() {
        for (int f = 0; f < 9; f++){
            for (int c = 0; c < 9; c++){
 		tablero[f][c] = 0;
            }
 	}
    }   
    

    public void limpiarMatriz() {
        for (int f = 0; f < 9; f++){
            for (int c = 0; c < 9; c++){
 		matriz[f][c] = 0;
            }
 	}
    }   
    
    /** Insertamos el valor indicado en los parámetros en la posición indicada por los mismo.
    * @param fila fila en la que insertaremos el valor.
    * @param col *columna en la que insertaremos el valor.
    * @param valor valor que queremos insertar en la celda determinada por la columna y fila dadas.
    */
    public void insertarValor(int fila, int col, int valor) {
        tablero[fila][col] = valor;
    }



    // Método que comprueba si el número (valor) insertado se encuentra en alguna fila, columna o caja.
    public boolean Valido() {
        // Comprobamos si está en su fila.
        for (int f = 0; f < 9; f++) {
            boolean[] Insertado = new boolean[10]; // Vector que contiene a las celdas de una fila. Vale "false" si el número no está en esa celda y "true" si sí que lo está.
            for (int aux = 0; aux < 10; aux++)
 		Insertado[aux] = false;
		for (int c = 0; c < 9; c ++) {
                    if ((tablero[f][c]) == 0) continue; // Si la celda tiene valor "0", está vacía y continuamos.
                    else {
 			if (Insertado[tablero[f][c]]){
                            // La celda tiene el número que íbamos a poner en otra de la misma fila, por tanto, no se puede poner.
                            return false;
			}
			else Insertado[tablero[f][c]] = true; // Marca el número como usado.
                    }
 		}
 	}
 	// Comprobamos si está en su columna.
 	for (int c = 0; c < 9; c++) {
            boolean[] Insertado = new boolean[10]; // Vector que contiene a las celdas de una columna. Vale "false" si el número no está en esa celda y "true" si sí que lo está.
            for (int aux = 0; aux < 10; aux++)
                Insertado[aux] = false;
 		for (int f = 0; f < 9; f ++) {
                    if ((tablero[f][c]) == 0) continue; // Si la celda tiene valor "0", está vacía y continuamos.
                    else {
			if (Insertado[tablero[f][c]]) {
                            // La celda tiene el número que íbamos a poner en otra de la misma columna, por tanto, no se puede poner.
                            return false;
			}
 			else Insertado[tablero[f][c]] = true; // Marca el número como usado.
                    }
 		}
 	}
 	// Comprobamos si está en su caja.
 	for (int bf = 0; bf < 3; bf++) {
            for (int bc = 0; bc < 3; bc++) { // Para nueve cajas.
 		boolean[] Insertado = new boolean[10]; // Vector que contiene a las celdas de una caja. Vale "false" si el número no está en esa celda y "true" si sí que lo está.
 		for (int aux = 0; aux < 10; aux++)
                    Insertado[aux] = false;
                    for (int f = 0; f < 3; f++) { // Para cada celda en una caja.
 			for (int c = 0; c < 3; c++) {
                            if ((tablero[bf*3 + f][bc*3 + c]) == 0) continue; // Si la celda tiene valor "0", está vacía y continuamos.
				if (Insertado[(tablero[bf*3 + f][bc*3 + c])]) {
                                    return false; // Si el número está usado, el Sudoku no es válido.
 				} else {
                                    Insertado[(tablero[bf*3 + f][bc*3 + c])] = true; // Si el número no está usado, se marca como usado.
				}
 			}
                    }
		}
	}
	return true; //El sudoku es válido.
    }

 
    public boolean resolver(){
 	int flibre, clibre; // Siguiente fila o culumna a rellenar.
 	int []CeldaLibre = new int[2];
 	CeldaLibre = nextCeldaLibre();
 	if (CeldaLibre[0] < 0 && CeldaLibre[1] < 0){
            return this.Valido(); // No hay más celdas a rellenar y el Sudoku es válido. ¡Sudoku resuelto!
 	}
 	else {
            flibre=CeldaLibre[0];
            clibre=CeldaLibre[1];
            return resolver(flibre,clibre);
	}
    }

 
    private boolean resolver(int fila, int columna){
 	int []nextLibre;
	boolean resuelto = false;
 	for (int numero=1; numero< 10; numero++){
            this.insertarValor(fila,columna,numero);
            if (this.Valido()){
		nextLibre = nextCeldaLibre();
 		if (nextLibre[0] < 0) return true;
                    else{
			count++;
 			resuelto = resolver(nextLibre[0],nextLibre[1]);
			if (resuelto) return true;
                    }
		}
		this.insertarValor(fila,columna,0);
	}
 	return false;
    }


    private int[] nextCeldaLibre(){
 	// La posición [0] del vector devuelve la fila; la posición [1] del vector devuelve la columna.
 	int[] nextLibre = new int[2];
 	nextLibre[0]=-1;
 	nextLibre[1]=-1;
 	for (int fila =0; fila < 9; fila++){
            for(int col=0; col < 9; col++){
 		if (tablero[fila][col] == 0){
                    nextLibre[0]= fila;
                    nextLibre[1]= col;
 		}
            }
        }
	return nextLibre;
    }

 
    public int[][] sacarTablero(){
 	return tablero;
    }

 
/************************************************Fin código de resolución de sudokus******************************************************/



/****************************************************Inicio códigos aleatorios************************************************************/



    /** Crea n números aleatorios.
    * @param n cantidad de números aleatorios a crear.
    * @return un vector con los n números aleatorios.
    */

     public int[] aleatorio (int n) {
	int[] numeros = new int[n];
 	for (int i = 0; i < n; i++) {
 		numeros[i] = rnd.nextInt(9)+1;
 	}return numeros;
    }  

 

    /** Crea 1 número aleatorio del 0 al 8.
    * @return el núemro aleatorio.
    **/
 
    public int lineasAleatorias () {
	int lineas = rnd.nextInt(9);
 	return lineas;
    }



    /** Crea un número aleatorio.
    * @return un número aleatorio entre 5 y 15.
    */ 

    public int aleatorio(){
 	int aleatorio = rnd.nextInt(11)+5;
            return aleatorio;
    }

    //Inserta un número de valores aleatorios en celdas aleatorias del sudoku.

    public void insertarAleatorio(){
 	int n = 5; // Número de valores iniciales a insertar en el sudoku
        valoresAl = this.aleatorio(n);
        while(this.comprobarValores(valoresAl)==false) valoresAl=this.aleatorio(n);
 	for (int i = 0; i < n; i++){
 		int valor=valoresAl[i];
                this.posicionesAleatorias(); 
 		this.insertarValor(aFila, aCol, valor);
		if(this.Valido()==false){
 			insertarValor(aFila, aCol, 0);
			i=i-1;
                }
	}
    }

    private void posicionesAleatorias(){
 	this.aFila = lineasAleatorias();
 	this.aCol = lineasAleatorias();
    }

 

    /** Comprueba que no se intente insertar un valor más de 9 veces.
    *  @param valores vector con los valores a insertar.
    *  @return Devuelve false si se intentan insertar un valor más de 9 veces.
    **/
    public boolean comprobarValores(int[] valores){
 	for (int i=0;i<valores.length-1;i++){
            for (int j=i+1;j<valores.length;j++){
 		if (valores[i]==valores[j]){
                    comp[valores[i]]=comp[valores[i]]+1;
                    if(comp[valores[i]]+1==10) return false;
 		}
            }
 	}
 	return true;
    }


    /** Resuelve n sudokus y mira a ver si son válidos o no.
    * @param n número de susokus a resolver.
    * @return el número de sudokus resueltos.
    **/  
    public Object[] sudokusResueltos(int n){
 	int r=0;
	Object[] matrizAl = new Object[n];
 	this.limpiar();
 	for (int i=0; i<n; i++){
           count=0;
            this.insertarAleatorio();
            if (this.resolver()){
                r=r+1;
		matrizAl [i] = tablero; 
            }
 	}
	return matrizAl;
    }


    public int getaFila(){
	return aFila;
    }

    public int getCol(){
	return aCol;
    }
}
